﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace LiveFrameworkExplorer.Plugins.LiveFxBrowser
{
    public class JsonItem
    {
        #region Fields

        public List<JsonItem> Children;
        private static Regex regexArrayBegin = new Regex(@"(?x) ^(?<s>\s*) (?<v>\[)");
        private static Regex regexArrayEnd = new Regex(@"(?x) ^(?<s>\s*) (?<v>\])");
        private static Regex regexMemberSep = new Regex(@"(?x) ^(?<s>\s*) (?<v>,)");
        private static Regex regexNameSep = new Regex(@"(?x) ^(?<s>\s*) (?<v>:)");
        private static Regex regexNonWhite = new Regex(@"(?x) ^\S");
        private static Regex regexNumber = new Regex(@"(?x) ^(?<s>\s*) (?<v>-? (0|[1-9]\d*) (\.\d+)? ([eE][+-]?\d+)?)");
        private static Regex regexObjectBegin = new Regex(@"(?x) ^(?<s>\s*) (?<v>{)");
        private static Regex regexObjectEnd = new Regex(@"(?x) ^(?<s>\s*) (?<v>})");
        private static Regex regexString = new Regex(@"(?x) ^(?<s>\s*) (?<v>\x22 ([^\\\x22]|\\[\\\x22/bfnrt]|\\u[0-9a-fA-F]{4}|\\@)* \x22)");
        private static Regex regexValue = new Regex(@"(?x) ^(?<s>\s*) (?<v>null|true|false)[\s,}\]]");
        public JsonItemType Type;
        public string Value;

        #endregion

        #region Methods

        public JsonItem(JsonItemType type, List<JsonItem> children, string value)
        {
            this.Type = type;
            this.Children = children;
            this.Value = value;
        }

        public static JsonItem Load(string json, int start)
        {
            JsonItem item = Parse(json, ref start);
            if (regexNonWhite.Match(json, start, json.Length - start).Success)
            {
                throw new JsonItemException();
            }
            return item;
        }

        private static JsonItem Parse(string json, ref int start)
        {
            while (char.IsWhiteSpace(json[start]))
            {
                start++;
            }
            if (json[start] == '{')
            {
                return ParseObject(json, ref start);
            }
            if (json[start] == '[')
            {
                return ParseArray(json, ref start);
            }
            return ParseValue(json, ref start);
        }

        private static JsonItem ParseArray(string json, ref int start)
        {
            Match match = regexArrayBegin.Match(json, start, json.Length - start);
            if (!match.Success)
            {
                throw new JsonItemException();
            }
            JsonItem item = new JsonItem(JsonItemType.Array, new List<JsonItem>(), null);
            start += match.Groups["s"].Length + match.Groups["v"].Length;
            if (!regexArrayEnd.Match(json, start, json.Length - start).Success)
            {
                do
                {
                    JsonItem item2 = Parse(json, ref start);
                    item.Children.Add(item2);
                    match = regexMemberSep.Match(json, start, json.Length - start);
                    if (match.Success)
                    {
                        start += match.Groups["s"].Length + match.Groups["v"].Length;
                    }
                }
                while (match.Success);
            }
            match = regexArrayEnd.Match(json, start, json.Length - start);
            if (!match.Success)
            {
                throw new JsonItemException();
            }
            start += match.Groups["s"].Length + match.Groups["v"].Length;
            return item;
        }

        private static JsonItem ParseObject(string json, ref int start)
        {
            Match match = regexObjectBegin.Match(json, start, json.Length - start);
            if (!match.Success)
            {
                throw new JsonItemException();
            }
            JsonItem item = new JsonItem(JsonItemType.Object, new List<JsonItem>(), null);
            start += match.Groups["s"].Length + match.Groups["v"].Length;
            do
            {
                match = regexString.Match(json, start, json.Length - start);
                if (match.Success)
                {
                    start += match.Groups["s"].Length + match.Groups["v"].Length;
                    JsonItem item2 = new JsonItem(JsonItemType.Member, new List<JsonItem>(), match.Groups["v"].Value);
                    match = regexNameSep.Match(json, start, json.Length - start);
                    if (!match.Success)
                    {
                        throw new JsonItemException();
                    }
                    start += match.Groups["s"].Length + match.Groups["v"].Length;
                    JsonItem item3 = Parse(json, ref start);
                    item2.Children.Add(item3);
                    item.Children.Add(item2);
                    match = regexMemberSep.Match(json, start, json.Length - start);
                    if (match.Success)
                    {
                        start += match.Groups["s"].Length + match.Groups["v"].Length;
                    }
                }
            }
            while (match.Success);
            match = regexObjectEnd.Match(json, start, json.Length - start);
            if (!match.Success)
            {
                throw new JsonItemException();
            }
            start += match.Groups["s"].Length + match.Groups["v"].Length;
            return item;
        }

        private static JsonItem ParseValue(string json, ref int start)
        {
            Match match = regexValue.Match(json, start, json.Length - start);
            if (match.Success)
            {
                start += match.Groups["s"].Length + match.Groups["v"].Length;
                return new JsonItem(JsonItemType.Value, null, match.Groups["v"].Value);
            }
            match = regexNumber.Match(json, start, json.Length - start);
            if (match.Success)
            {
                start += match.Groups["s"].Length + match.Groups["v"].Length;
                return new JsonItem(JsonItemType.Value, null, match.Groups["v"].Value);
            }
            match = regexString.Match(json, start, json.Length - start);
            if (!match.Success)
            {
                throw new JsonItemException();
            }
            start += match.Groups["s"].Length + match.Groups["v"].Length;
            return new JsonItem(JsonItemType.Value, null, match.Groups["v"].Value);
        }

        private static void PrintItem(JsonItem item, StringBuilder sb, int indent, int indentDelta)
        {
            switch (item.Type)
            {
                case JsonItemType.Object:
                    sb.AppendLine("{");
                    indent += indentDelta;
                    sb.Append(' ', indent);
                    for (int i = 0; i < item.Children.Count; i++)
                    {
                        PrintItem(item.Children[i], sb, indent, indentDelta);
                        if (i < (item.Children.Count - 1))
                        {
                            sb.Append(",");
                            sb.AppendLine();
                            sb.Append(' ', indent);
                        }
                    }
                    indent -= indentDelta;
                    sb.AppendLine();
                    sb.Append(' ', indent);
                    sb.Append("}");
                    return;

                case JsonItemType.Array:
                    sb.AppendLine("[");
                    indent += indentDelta;
                    sb.Append(' ', indent);
                    for (int j = 0; j < item.Children.Count; j++)
                    {
                        PrintItem(item.Children[j], sb, indent, indentDelta);
                        if (j < (item.Children.Count - 1))
                        {
                            sb.Append(",");
                            sb.AppendLine();
                            sb.Append(' ', indent);
                        }
                    }
                    indent -= indentDelta;
                    sb.AppendLine();
                    sb.Append(' ', indent);
                    sb.Append("]");
                    return;

                case JsonItemType.Member:
                    sb.Append(item.Value);
                    sb.Append(" : ");
                    PrintItem(item.Children[0], sb, indent, indentDelta);
                    return;

                case JsonItemType.Value:
                    sb.Append(item.Value);
                    return;
            }
            throw new JsonItemException();
        }

        public string ToFormattedString(int indentDelta)
        {
            StringBuilder sb = new StringBuilder();
            PrintItem(this, sb, 0, indentDelta);
            return sb.ToString();
        }

        #endregion

        #region Nested Types

        public class JsonItemException : Exception
        {
        }

        public enum JsonItemType
        {
            Object,
            Array,
            Member,
            Value
        }

        #endregion
    }
}
